Skip to content

Merge dotnet/java-interop into external/Java.Interop with full history#11744

Merged
jonathanpeppers merged 1514 commits into
mainfrom
jonathanpeppers-java-interop-migration-plan
Jul 1, 2026
Merged

Merge dotnet/java-interop into external/Java.Interop with full history#11744
jonathanpeppers merged 1514 commits into
mainfrom
jonathanpeppers-java-interop-migration-plan

Conversation

@jonathanpeppers

@jonathanpeppers jonathanpeppers commented Jun 25, 2026

Copy link
Copy Markdown
Member

⚠️ MUST be merged with "Create a merge commit" — not squash, not rebase

dotnet/android does not normally allow merge commits. The maintainer landing
this PR needs to JIT-elevate to temporarily enable the merge-commit
option, land the PR, and then revert the setting. If this PR is squashed or
rebased, the full per-commit authorship history from dotnet/java-interop is
lost.

What this does

Replaces the external/Java.Interop git submodule with an in-tree copy of
the entire dotnet/java-interop history, rewritten so every commit's tree
lives under external/Java.Interop/.

  • 1511 commits brought in from dotnet/java-interop@main, up to and
    including the SHA the submodule pointer currently references on
    dotnet/android/main (6ec1345165)
  • Original author, committer, author-date, committer-date, and message
    preserved verbatim on every commit — no Co-authored-by trailers added
    (dates span 2014-01-02 .. 2026-06-26)
  • Commit SHAs change because trees change (paths rewritten under
    external/Java.Interop/); this is inherent to subdirectory filtering

How it was done

git clone https://github.com/dotnet/java-interop ji-rewrite
cd ji-rewrite
git checkout 6ec1345165          # SHA main's submodule pointer references
git filter-repo --to-subdirectory-filter external/Java.Interop

then in this branch:

git submodule deinit -f external/Java.Interop
git rm -f external/Java.Interop                                    # commit 1
git merge --allow-unrelated-histories --no-ff ji-rewrite/main      # merge commit
# add CI plumbing + adjust in-tree paths                            # commit 3

Commits on this branch (3 on top of origin/main)

* 7fc6649ea Add Java.Interop CI stage and merge follow-ups
* c9f3e48e3 Merge dotnet/java-interop history under external/Java.Interop
|\
| * (1511 commits, original authors + dates: 2014-01-02 .. 2026-06-26)
* 0b1898fd5 Remove external/Java.Interop submodule in preparation for in-tree merge

Adjustments commit — what's in it

The single follow-up commit on top makes the minimal changes needed for
in-tree paths + CI:

  • .gitmodules — drop external/Java.Interop, add nested
    external/Java.Interop/external/xamarin-android-tools
  • build-tools/scripts/XAVersionInfo.targets — drop Java.Interop from
    _SubmoduleBranchInfo
  • src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets
    @JAVA_INTEROP_COMMIT@ → literal in-tree
  • build-tools/automation/yaml-templates/cache-gradle.yaml — use
    git log -1 --format=%H external/Java.Interop instead of submodule status
  • New build-tools/automation/yaml-templates/stage-java-interop-tests.yaml
    (see CI changes below)
  • external/Java.Interop/build-tools/automation/templates/{core-build,core-tests,run-dotnet-test}.yaml
    — add workingDirectory: external/Java.Interop and full-path project
    globs so the templates work when driven from the dotnet/android repo root
  • Delete 9 unused yml files under external/Java.Interop/ (dependabot,
    GitHub Actions workflows, onelocbuild.yaml, the upstream
    azure-pipelines.yaml, publish-test-results.yaml) — none are wired
    into anything on the dotnet/android side

CI changes

Adds a shared stage template
build-tools/automation/yaml-templates/stage-java-interop-tests.yaml that
mirrors the two jobs from the upstream ji pipeline:

  • Java.Interop Tests > Windows - .NETnativeAotRid: win-x64
  • Java.Interop Tests > Mac - .NETnativeAotRid: osx-arm64 (Apple
    Silicon), with a guarded brew install cmake step

The stage is wired into both pipelines (zero duplication):

  • build-tools/automation/azure-pipelines.yaml (official / 1ES) — uses
    default hosted pools
  • build-tools/automation/azure-pipelines-public.yaml (public PR
    validation) — Windows on NetCore-Public
    (WindowsPoolImageNetCorePublic), Mac on AcesShared
    (ACES_VM_SharedPool_Tahoe)

Build artifact publishing was intentionally dropped from the ji lanes —
only logs + test results are surfaced (matches upstream ji behavior; the
binaries aren't consumed downstream).

Verified

  • ji content parity vs upstream 6ec1345165: 1,577 / 1,580 shared
    files byte-identical. The 3 mismatches are the ji CI templates
    intentionally modified for in-tree paths (see above). 9 files
    intentionally absent (the deleted-yml list above). 0 files "only in
    ours".
  • dotnet/android output binaries: compared against a same-day
    baseline build with no ji changes — all 7 artifacts match; largest
    delta was 68 bytes on a 145 MB nupkg. windows-toolchain-pdb
    byte-identical.
  • ji lane parity: step lists identical to the upstream ji pipeline
    running the same SHA.

Follow-up PR (not this one)

  • Reorganize / consolidate code under external/Java.Interop/
  • Dedupe the nested external/Java.Interop/external/* submodules against
    dotnet/android's own external/* (e.g. xamarin-android-tools)
  • Delete code that's superseded by what already lives in dotnet/android

Checklist for the merger

  • JIT-elevate to enable "Create a merge commit" on dotnet/android
  • Click Create a merge commit (NOT squash, NOT rebase)
  • Revert the merge-commit setting after the PR lands
  • Coordinate archiving / read-only of dotnet/java-interop separately

pjcollins and others added 30 commits July 12, 2023 14:47
Fixes: dotnet/java-interop#1071

The latest API docs update contained a couple dozen parsing issues
due to `<code/>` parsing, including:

  * Closing element doesn't match opening element: `<code>null</null>`
  * Content including `@`: `<code>android:label="@string/resolve_title"</code>`
  * Closing element is actually an opening element:
    `<code>Activity.RESULT_OK<code>`
  * Improper element nesting: `<code><pre><p>content</code></pre></p>`
  * Use of attributes: `<code class=prettyprint>content<code>`

Fix this by replacing `CodeElementDeclaration` to use a new
`CodeElementContentTerm` terminal, which is a "greedy regex" which
grabs `<code` until one of:

  * `</code>`
  * `</null>`
  * `<code>`

The result of `CodeElementDeclaration` is the end of the `<code>`
element until the beginning of one of the above terminators:

  * `<code>null</null>` becomes `<c>null</c>`
  * `<code>android:label="@string/resolve_title"</code>` becomes
    `<c>android:label="@string/resolve_title"</c>`.`
  * `<code>Activity.RESULT_OK<code>` becomes `<c>Activity.RESULT_OK</c>`.
  * `<code><pre><p>content</code></pre></p>` becomes the mess
    `<c>&lt;pre&gt;&lt;p&gt;some content</c>&lt;/pre&gt;&lt;/p&gt;` 🤷‍♂️
  * `<code class=prettyprint>content<code>` becomes `<c>content</c>`.`
Context: a8d50a5
Context: ff1855f

As part of the Nullable Reference Type work in ff1855f, we added a
BG8A08 warning when the `//*/@path` attribute of a "metadata" element
is not provided.  However, metadata files can also contain
`<ns-replace/>` elements, which do not have a `@path` attribute, and
thus erroneously trigger this warning:

	generated\msbuild-metadata.xml(3,4): warning BG8A08:
	Metadata.xml element '<ns-replace source="com.google.androidx" replacement="Xamarin.AndroidX" />' is missing the 'path' attribute.

Skip running any of the standard "metadata" logic when we hit a
`<ns-replace>` element, as they are handled elsewhere.
…1137)

As we consume nightly .NET 8 builds, they sometimes depend on nightly
.NET 7 builds.

One error you can run into is:

	error NU1102: Unable to find package Microsoft.AspNetCore.App.Ref with version (= 7.0.11)
	error NU1102: Unable to find package Microsoft.WindowsDesktop.App.Ref with version (= 7.0.11)

For projects that are not even ASP.NET or Windows desktop apps!
To even be able to access these feeds, they would need to be an entry
within `NuGet.config` similar to

	<packageSources>
	  <clear/>
	  <add key="darc-pub-dotnet-aspnetcore-[SHA]" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-aspnetcore-[SHA]/nuget/v3/index.json" />
	  <add key="darc-pub-dotnet-windowsdesktop-[SHA]" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-windowsdesktop-[SHA]/nuget/v3/index.json" />
	</packageSources>

We don't currently track these packages, because we don't actually
use them.

The .NET SDK team has provided a setting to workaround this,
[`$(DisableTransitiveFrameworkReferenceDownloads)`][0], we have been
[using in xamarin/xamarin-android for some time][1].

Let's do the same here to avoid this problem as seen in 4f9dbce6.

[0]: https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#disabletransitiveframeworkreferencedownloads
[1]: https://github.com/xamarin/xamarin-android/blob/6768c731d327c8148c45304c895ca8987a9cc2f1/Directory.Build.props#L26-L27
…1138)

Context: #8279

This reverts commit 83b5089.

Near the end of .NET 8 RC 1, the .NET 8 SDK depends on nightly
packages for .NET 6 and .NET 7:

  * dotnet/runtime 7.0.11
  * dotnet/runtime 6.0.22

These come from feeds within `NuGet.config` such as:

	<packageSources>
	  <clear/>
	  <!-- Added manually for dotnet/runtime 7.0.11 -->
	  <add key="darc-pub-dotnet-runtime-a2ad4f0" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-a2ad4f03/nuget/v3/index.json" />
	  <!-- Added manually for dotnet/runtime 6.0.22 -->
	  <add key="darc-pub-dotnet-runtime-762f437" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-762f4379/nuget/v3/index.json" />
	</packageSources>

The version number is stable, so the .NET releng team has
infrastructure to create a new feed per git commit.

Unfortunately, this makes NuGet central package management unusable
for us. 😢

As seen in #8279, we get errors like:

	Package source mapping matches found for package ID 'Microsoft.NETCore.App.Ref' are: 'dotnet-public'.
	…
	external\Java.Interop\src\Java.Interop.Tools.Expressions\Java.Interop.Tools.Expressions.csproj error NU1102: Unable to find package Microsoft.NETCore.App.Ref with version (= 7.0.11)
	- Found 83 version(s) in dotnet-public [ Nearest version: 8.0.0-preview.1.23110.8 ]
	- Versions from dotnet-eng were not considered [Xamarin.Android.sln]

This is because it can only possibly resolve this package from
`dotnet-public`:

	<packageSourceMapping>
	  <packageSource key="dotnet-public">
	    <package pattern="*" />
	  </packageSource>
	</packageSourceMapping>

Because 7.0.11 hasn't shipped yet, it is on neither NuGet.org nor
`dotnet-public`.

For this to work, we would somehow need this `NuGet.config` fragment
to be added to the xamarin/java.interop repo whenever
xamarin/xamarin-android gets a newer .NET 8 SDK:

	<packageSourceMapping>
	  <packageSource key="darc-pub-dotnet-runtime-[HASH]">
	    <package pattern="Microsoft.NETCore.App.Ref" />
	  </packageSource>
	</packageSourceMapping>

For now, let's revert 83b5089.  Maybe there is some solution we can
come up with to use this in the future.
Update `<JdkInfo/>` task to emit new `$(Java*MajorVersion)`
and `$(JavaApi*DefineConstants)` MSBuild properties.  These are used
by `src/Java.Base` so that it knows which JDK version it's binding.

Update `src/Java.Base` to support binding the `java.base.jmod` from
JDK 17.

Note: This "JDK-17 Java.Base binding" was a "time limited" effort.

To build against JDK-17:

 1. Install JDK-17.

 2. Prepare and override `$(JdksRoot)`:

        dotnet build -t:Prepare Java.Interop.sln -p:JdksRoot=/Library/Java/JavaVirtualMachines/microsoft-17.jdk/Contents/Home

    will use the Microsoft OpenJDK 17 installation on macOS.

 3. Build:

        dotnet build Java.Interop.sln
Changes: dotnet/android-tools@3cee10b...9c50a2d

  * dotnet/android-tools@9c50a2d: [build] set `$(DisableTransitiveFrameworkReferenceDownloads)`=true (dotnet/android-tools#216)
  * dotnet/android-tools@52f0866: [Xamarin.Android.Tools.AndroidSdk] Check all <intent-filter/>s (dotnet/android-tools#214)
  * dotnet/android-tools@57be026: [Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-34 (dotnet/android-tools#211)
  * dotnet/android-tools@0a9ea47: [Xamarin.Android.Tools.AndroidSdk] Add API-34 to KnownVersions (dotnet/android-tools#212)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Context: https://discord.com/channels/732297728826277939/732297837953679412/1151531791069499524

A user reported the following output in their `java-resolution-report.log`:

	The field 'Java.Interop.Tools.JavaTypeSystem.Models.JavaFieldModel' was removed because its name contains a dollar sign.
	The class '[Class] com.google.android.libraries.navigation.internal.aac.ad' was removed because the Java base type 'com.google.android.libraries.navigation.internal.aad.ar<com.google.android.libraries.navigation.internal.aac.af<K, V>>' could not be found.

We should be providing the user with the name of the removed field
rather than the `JavaFieldModel` type name.  To do this, add an
appropriate `JavaFieldModel.ToString ()` method override.
Changes: dotnet/android-tools@9c50a2d...8a971d9

  * dotnet/android-tools@8a971d9: Merge pull request dotnet/android-tools#217 from xamarin/dev/tondat/main-openjdkms
  * dotnet/android-tools@42bbef8: Update OpenJDK location for OpenJDK17 on windows

Adds support to look for JDK installation on Windows within
`%ProgramFiles%\Android\openjdk\jdk-*`.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Context: #8360
Context: c1ff50a
Context: f91da28
Context: dotnet/android-tools@34e98e2

When .NET 8 RC 2 took a dependency on dotnet/runtime 7.0.12 and 6.0.23,
we added `external/xamarin-android-tools.override.props` in
#8360 with the contents:

	<Project>
	  <PropertyGroup>
	    <RestoreAdditionalProjectSources>
	      https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-26e0f822/nuget/v3/index.json;
	      https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-runtime-301ba1ee/nuget/v3/index.json;
	    </RestoreAdditionalProjectSources>
	  </PropertyGroup>
	</Project>

This allowed xamarin-android/external/xamarin-android-tools to find
and use the new NuGet sources, but
xamarin-android/external/Java.Interop uses its own checkout of
xamarin-android-tools in
`xamarin-android/external/java.interop/external/xamarin-android-tools`.`

This led to the error during the `prepare java.interop Debug` stage:

	tests/api-compatibility/api-compatibility.targets(3,3): warning MSB4011: "Configuration.props" cannot be imported again. It was already imported at "build-tools/scripts/RunTests.targets (7,3)". This is most likely a build authoring error. This subsequent import will be ignored.
	external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Ref with version (= 6.0.23)
	external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 86 version(s) in dotnet-public [ Nearest version: 7.0.0-preview.1.22076.8 ]
	external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 1 version(s) in dotnet-eng [ Nearest version: 5.0.0-alpha.1.19618.1 ]
	external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: Unable to find package Microsoft.NETCore.App.Ref with version (= 6.0.23)
	external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 86 version(s) in dotnet-public [ Nearest version: 7.0.0-preview.1.22076.8 ]
	external/Java.Interop/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj : error NU1102: - Found 1 version(s) in dotnet-eng [ Nearest version: 5.0.0-alpha.1.19618.1 ]
	build-tools/scripts/DotNet.targets(19,5): error MSB3073: The command ""bin/Release/dotnet/dotnet" build -t:Prepare Java.Interop.sln -c Debug -p:JdksRoot= -p:DotnetToolPath=bin/Release/dotnet/dotnet -bl:build-tools/scripts/../../bin/BuildDebug/msbuild-20230925T183954-prepare-java-interop.binlog" exited with code 1.
	    1 Warning(s)
	    7 Error(s)

Introduce an `external/xamarin-android-tools.override.props` within
Java.Interop which imports `..\Directory.Build.props`.  This allows
MSBuild properties to "flow" from a "parent"
`xamarin-android/external/Java.Interop.override.props` through to
`xamarin-android/external/Java.Interop/external/xamarin-android-tools`,
allowing a xamarin-android checkout to more easily control MSBuild
properties used by xamarin-android-tools.
…1143)

Fixes: dotnet/java-interop#1142

Context: b116a4b

In b116a4b, we added a feature to automatically mark a method as
"deprecated" if it overrides a "deprecated" method.

However, there can be instances where this is undesirable for a user,
and there is currently no way to opt out of this change on a global
or `metadata` level.

Add a global opt-out for this feature, usable via
`generator --lang-features=do-not-fix-obsolete-overrides …`.

This will eventually be made available to users via an MSBuild
property in a separate xamarin/xamarin-android PR.
Fixes: #8337

A customer's app with the code:

	SetContentView(Resource.Layout.activity_main);
	FindViewById<Button>(Resource.Id.asd).Click += MainActivity_Click;

Crashes with `-c Release -p:AndroidLinkMode=r8` with:

	java.lang.RuntimeException: Unable to start activity ComponentInfo{com.companyname.New_folder/crc64abe8cc9139195b67.MainActivity}: java.lang.ClassNotFoundException: android.view.View_IOnClickListenerImplementor
	    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3644)
	    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3781)
	    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101)
	    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:138)
	    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
	    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2306)
	    at android.os.Handler.dispatchMessage(Handler.java:106)
	    at android.os.Looper.loopOnce(Looper.java:201)
	    at android.os.Looper.loop(Looper.java:288)
	    at android.app.ActivityThread.main(ActivityThread.java:7918)
	    at java.lang.reflect.Method.invoke(Native Method)
	    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
	    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
	Caused by: java.lang.ClassNotFoundException: android.view.View_IOnClickListenerImplementor
	    at crc64abe8cc9139195b67.MainActivity.n_onCreate(Native Method)
	    at crc64abe8cc9139195b67.MainActivity.onCreate(MainActivity.java:30)
	    at android.app.Activity.performCreate(Activity.java:8342)
	    at android.app.Activity.performCreate(Activity.java:8321)
	    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1417)
	    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3625)
	    ... 12 more

Which, can be solved by adding your own `proguard` rules like:

	-keep class android.view.View_IOnClickListenerImplementor { *; }

In a8cbe01, we thought (incorrectly):

> Additionally, stop emitting the `[Register]` attribute for
> `*Implementor` classes:
>
>     [global::Android.Runtime.Register ("mono/android/view/View_OnFocusChangeListenerImplementor")]
>     partial class IOnFocusChangeListenerImplementor {/* … */}
>
> The `[Register]` attribute is not needed, because `*Implementor`
> classes are generated internal implementation details.

`proguard_xamarin.cfg` has the entry:

	-keep class mono.android.** { *; <init>(...); }

We could do `-keep class android.**` [^0], but that would certainly
preserve way too much!

For now, let's just restore `[Register]` to revisit this issue at a
later date -- maybe .NET 9?

[^0]: Why is `View_IOnClickListenerImplementor` in the `android.view`
      package?!  Because package name generation for Java Callable
      Wrappers is special-cased for `Mono.Android` to simply
      [lowercase the namespace name][0], which applies only if the 
      type *doesn't* have `[RegisterAttribute]`!

[0]: https://github.com/xamarin/java.interop/blob/009f9c03317d1ef0c0a66fffe2b5c654cb9fd2e1/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs#L200-L209
Context: 59c5d93
Context: https://discord.com/channels/732297728826277939/732297837953679412/1162418256615846030
Context: https://discord.com/channels/732297728826277939/732297837953679412/1162422742256205954

A customer reports that when binding
[androidx.emoji2.emoji2-emojipicker-1.4.0][0],
[`EmojiPickerView.setOnEmojiPickedListener()`][1] wasn't being bound.
Subsequent investigation showed that it wasn't being bound because it
had `//method[@visibility="kotlin-internal"]`.

…but *why* did `class-parse` indicate that
`EmojiPickerView.setOnEmojiPickedListener()` has visibility of
`kotlin-internal` and not `public`?

In order to assist this question, update `class-parse -dump` to also
dump out the parsed Kotlin metadata blob.

Example:

	% dotnet …/class-parse.dll -dump EmojiPickerView.class
	…
	Kotlin Class Metadata [1.8.0]: {
	  "$id": "1",
	  "CompanionObjectName": "Companion",
	  "Constructors": { ... },
	  "EnumEntries": {
	    "$id": "14",
	    "$values": []
	  },
	  "Flags": 6,
	  "FullyQualifiedName": null,
	  "Inheritability": 0,
	  "NestedClassNames": {
	    "$id": "15",
	    "$values": [
	      "Companion"
	    ]
	  },
	  "ObjectType": 0,
	  "SealedSubclassFullyQualifiedNames": null,
	  "SuperTypeIds": null,
	  "SuperTypes": {
	    "$id": "16",
	    "$values": [
	      {
	        "$id": "17",
	        "Arguments": {
	          "$id": "18",
	          "$values": []
	        },
	        "Nullable": false,
	        "FlexibleTypeCapabilitiesId": null,
	        "FlexibleUpperBound": null,
	        "FlexibleUpperBoundId": 0,
	        "ClassName": "android/widget/FrameLayout;",
	        "TypeParameter": null,
	        "TypeParameterName": null,
	        "TypeAliasName": null,
	        "OuterType": null,
	        "OuterTypeId": null,
	        "AbbreviatedType": null,
	        "AbbreviatedTypeId": null,
	        "Flags": 0
	      }
	    ]
	  },
	  "TypeParameters": {
	    "$id": "19",
	    "$values": []
	  },
	  "VersionRequirements": null,
	  "Visibility": 3,
	  "Functions": {
	    "$id": "20",
	    "$values": [
	      ...
	      {
	        "$id": "145",
	        "Name": "setOnEmojiPickedListener",
	        "JvmName": "setOnEmojiPickedListener",
	        "JvmSignature": null,
	        "Flags": 6,
	        "ReturnType": {
	          "$id": "146",
	          "Arguments": {
	            "$id": "147",
	            "$values": []
	          },
	          "Nullable": false,
	          "FlexibleTypeCapabilitiesId": null,
	          "FlexibleUpperBound": null,
	          "FlexibleUpperBoundId": 0,
	          "ClassName": "kotlin/Unit",
	          "TypeParameter": null,
	          "TypeParameterName": null,
	          "TypeAliasName": null,
	          "OuterType": null,
	          "OuterTypeId": null,
	          "AbbreviatedType": null,
	          "AbbreviatedTypeId": null,
	          "Flags": 0
	        },
	        "ReturnTypeId": 0,
	        "TypeParameters": {
	          "$id": "148",
	          "$values": []
	        },
	        "ReceiverType": null,
	        "ReceiverTypeId": 0,
	        "TypeTable": null,
	        "Contract": null,
	        "ValueParameters": {
	          "$id": "149",
	          "$values": [
	            {
	              "$id": "150",
	              "Flags": 0,
	              "Name": "onEmojiPickedListener",
	              "Type": {
	                "$id": "151",
	                "Arguments": {
	                  "$id": "152",
	                  "$values": [
	                    {
	                      "$id": "153",
	                      "Projection": 2,
	                      "Type": {
	                        "$id": "154",
	                        "Arguments": {
	                          "$id": "155",
	                          "$values": []
	                        },
	                        "Nullable": false,
	                        "FlexibleTypeCapabilitiesId": null,
	                        "FlexibleUpperBound": null,
	                        "FlexibleUpperBoundId": 0,
	                        "ClassName": "androidx/emoji2/emojipicker/EmojiViewItem;",
	                        "TypeParameter": null,
	                        "TypeParameterName": null,
	                        "TypeAliasName": null,
	                        "OuterType": null,
	                        "OuterTypeId": null,
	                        "AbbreviatedType": null,
	                        "AbbreviatedTypeId": null,
	                        "Flags": 0
	                      },
	                      "TypeId": 0
	                    }
	                  ]
	                },
	                "Nullable": true,
	                "FlexibleTypeCapabilitiesId": null,
	                "FlexibleUpperBound": null,
	                "FlexibleUpperBoundId": 0,
	                "ClassName": "androidx/core/util/Consumer;",
	                "TypeParameter": null,
	                "TypeParameterName": null,
	                "TypeAliasName": null,
	                "OuterType": null,
	                "OuterTypeId": null,
	                "AbbreviatedType": null,
	                "AbbreviatedTypeId": null,
	                "Flags": 0
	              },
	              "TypeId": 0,
	              "VarArgElementType": null,
	              "VarArgElementTypeId": 0
	            }
	          ]
	        },
	        "VersionRequirements": null
	      },
	  ...
	  },
	  "Properties": {... },
	  "TypeAliases": {
	    "$id": "230",
	    "$values": []
	  },
	  "TypeTable": null,
	  "VersionRequirementTable": {
	    "$id": "231",
	    "Requirements": {
	      "$id": "232",
	      "$values": [
	        {
	          "$id": "233",
	          "Version": 25,
	          "VersionFull": 0,
	          "Level": 1,
	          "ErrorCode": 0,
	          "Message": 0,
	          "VersionKind": 0
	        }
	      ]
	    }
	  }
	}

	Kotlin Metadata String Table: [
	  "Landroidx/emoji2/emojipicker/EmojiPickerView;",
	  "Landroid/widget/FrameLayout;",
	  "context",
	  "Landroid/content/Context;",
	  "attrs",
	  "Landroid/util/AttributeSet;",
	  "defStyleAttr",
	  "",
	  "(Landroid/content/Context;Landroid/util/AttributeSet;I)V",
	  "_emojiGridRows",
	  "",
	  "Ljava/lang/Float;",
	  "bodyAdapter",
	  "Landroidx/emoji2/emojipicker/EmojiPickerBodyAdapter;",
	  "value",
	  "emojiGridColumns",
	  "getEmojiGridColumns",
	  "()I",
	  "setEmojiGridColumns",
	  "(I)V",
	  "emojiGridRows",
	  "getEmojiGridRows",
	  "()F",
	  "setEmojiGridRows",
	  "(F)V",
	  "emojiPickerItems",
	  "Landroidx/emoji2/emojipicker/EmojiPickerItems;",
	  "onEmojiPickedListener",
	  "Landroidx/core/util/Consumer;",
	  "Landroidx/emoji2/emojipicker/EmojiViewItem;",
	  "recentEmojiProvider",
	  "Landroidx/emoji2/emojipicker/RecentEmojiProvider;",
	  "recentItemGroup",
	  "Landroidx/emoji2/emojipicker/ItemGroup;",
	  "recentItems",
	  "",
	  "Landroidx/emoji2/emojipicker/EmojiViewData;",
	  "recentNeedsRefreshing",
	  "",
	  "scope",
	  "Lkotlinx/coroutines/CoroutineScope;",
	  "stickyVariantProvider",
	  "Landroidx/emoji2/emojipicker/StickyVariantProvider;",
	  "addView",
	  "",
	  "child",
	  "Landroid/view/View;",
	  "params",
	  "Landroid/view/ViewGroup$LayoutParams;",
	  "index",
	  "width",
	  "height",
	  "buildEmojiPickerItems",
	  "buildEmojiPickerItems$emoji2_emojipicker_release",
	  "createEmojiPickerBodyAdapter",
	  "refreshRecent",
	  "refreshRecent$emoji2_emojipicker_release",
	  "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;",
	  "removeAllViews",
	  "removeView",
	  "removeViewAt",
	  "removeViewInLayout",
	  "removeViews",
	  "start",
	  "count",
	  "removeViewsInLayout",
	  "setOnEmojiPickedListener",
	  "setRecentEmojiProvider",
	  "showEmojiPickerView",
	  "Companion",
	  "emoji2-emojipicker_release"
	]


[0]: https://maven.google.com/web/index.html#androidx.emoji2:emoji2-emojipicker:1.4.0
[1]: https://developer.android.com/develop/ui/views/text-and-emoji/emoji-picker#how-use
Fixes: dotnet/java-interop#1139

Context: https://jetbrains.gitbooks.io/kotlin-reference-for-kindle/content/properties.html

Kotlin does not allow classes to explicitly contain fields:

> Classes in Kotlin cannot have fields.

They can *implicitly* contain fields, but not explicitly.
Syntax that *look like* a field to those who don't know Kotlin:

	/* partial */ class EmojiPickerView {
	    private var onEmojiPickedListener: Consumer<EmojiViewItem>? = null
	}

are in fact *properties*, which may or may not involve a compiler-
generated backing field.

When a property is declared in Kotlin, Java `get*` and/or `set*`
methods are generated as needed.  In the case where the property is
`internal` -- which is not supported by Java -- `public` getters or
setters are generated.  We wish to "hide" these as they are not
intended to be part of the public API.  That is, they would not be
callable by Kotlin code.

However, consider these code fragments from [`EmojiPickerView.kt`][0]:

	/* partial */ class EmojiPickerView {
	    // Line 100
	    private var onEmojiPickedListener: Consumer<EmojiViewItem>? = null

	    // Lines 317-319
	    fun setOnEmojiPickedListener(onEmojiPickedListener: Consumer<EmojiViewItem>?) {
	        this.onEmojiPickedListener = onEmojiPickedListener
	    }
	}

Default member visibility in Kotlin is `public`, so this declares a
private `onEmojiPickedListener` property and a public
`setOnEmojiPickedListener()` method.

However, we were incorrectly determining visibility (59c5d93): we
treated `onEmojiPickedListener` and `setOnEmojiPickedListener()` as
if they were part of the same property.  As part of this association,
we saw that `onEmojiPickedListener` was private, and marked
`setOnEmojiPickedListener()` as private to follow suit.

This was incorrect, because `private` properties do not have setters
*at all*, so we should not attempt to hide anything for `private`
properties, only for `internal` properties.

With that incorrect association broken, that allows
`setOnEmojiPickedListener()` to be considered separately, and found
to have `public` visibility.

For example, this Kotlin code:

	private var type = 0
	internal var itype = 0

generates Java code equivalent to:

	private int type;
	private int itype;

	public final int getItype$main() {
	    return this.itype;
	}
	
	public final void setItype$main(int <set-?>) {
	this.itype = <set-?>;

Additionally, when matching `internal` properties to their getters
or setters, the generated name differs from the names given to
`public` getters and setters.

	// Kotlin: public var type = 0
	// Java:
	public final int getType () { ... }

	// Kotlin: internal var type = 0
	// Java:
	public final int getType$main () { ... }

Fix this scenario:

  * Do not attempt to hide getters/setters for `private` Kotlin
    properties.

  * Improve matching of generated names for `internal` Kotlin
    properties.

Additionally, our existing unit test in `NameShadowing.kt` was written
incorrectly:

	// Incorrect, return type of a function
	fun setType(type: Int) = { println (type); }

	// Correct, return type of void
	fun setType(type: Int) { println (type); }

The fixed `NameShadowing.kt` unit test fails without the other
changes here.  Additionally, add several more unit test cases to
cover `internal` properties and mangled getter/setter names.

Additionally, some enum values in `KotlinPropertyFlags` were
specified incorrectly which has been fixed.

[0]: https://github.com/androidx/androidx/blob/0d655214d339e006f4e13a85f55c78770c885f2e/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerView.kt
#1145)

Fixes: dotnet/java-interop#910

Context: d0996b0
Context: https://github.com/xamarin/java.interop/issues/858

Consider the Java `java.lang.Runnable` interface:

	package java.lang;
	public interface Runnable {
	    void run ();
	}

This is bound as:

	package Java.Lang;
	public interface IRunnable : IJavaPeerable {
	    void Run ();
	}

with some slight differences depending on whether we're dealing with
.NET Android (`generator --codegen-target=xajavainterop1`) or
`src/Java.Base` (`generator --codegen-target=javainterop1`).

Now, assume a Java API + corresponding binding which returns a
`Runnable` instance:

	package example;
	public class Whatever {
	    public static Runnable createRunnable();
	}

You can invoke `IRunnable.Run()` on the return value:

	IRunnable r = Whatever.CreateRunnable();
	r.Run();

but how does that work?

This works via an "interface Invoker", which is a class emitted by
`generator` which implements the interface and invokes the interface
methods through JNI:

	internal partial class IRunnableInvoker : Java.Lang.Object, IRunnable {
	    public void Run() => …
	}

Once Upon A Time™, the interface invoker implementation mirrored that
of classes: a static `IntPtr` field held the `jmethodID` value, which
would be looked up on first-use and cached for subsequent invocations:

	partial class IRunnableInvoker {
	    static IntPtr id_run;
	    public unsafe void Run() {
	        if (id_run == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "run", "()V");
	        JNIEnv.CallVoidMethod (Handle, id_run, …);
	    }
	}

This approach works until you have interface inheritance and methods
which come from inherited interfaces:

	package android.view;
	public /* partial */ interface ViewManager {
	    void addView(View view, ViewGroup.LayoutParams params);
	}
	public /* partial */ interface WindowManager extends ViewManager {
	    void removeViewImmediate(View view);
	}

This would be bound as:

	namespace Android.Views;
	public partial interface IViewManager : IJavaPeerable {
	    void AddView (View view, ViewGroup.LayoutParams @params);
	}
	public partial IWindowManager : IViewManager {
	    void RemoveViewImmediate (View view);
	}
	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    static IntPtr id_addView;
	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        if (id_addView == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
	        JNIEnv.CallVoidMethod (Handle, id_addView, …);
	    }
	}

Unfortunately, *invoking* `IViewManager.AddView()` through an
`IWindowManagerInvoker` would crash!

	D/dalvikvm( 6645): GetMethodID: method not found: Landroid/view/WindowManager;.addView:(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V
	I/MonoDroid( 6645): UNHANDLED EXCEPTION: Java.Lang.NoSuchMethodError: Exception of type 'Java.Lang.NoSuchMethodError' was thrown.
	I/MonoDroid( 6645): at Android.Runtime.JNIEnv.GetMethodID (intptr,string,string)
	I/MonoDroid( 6645): at Android.Views.IWindowManagerInvoker.AddView (Android.Views.View,Android.Views.ViewGroup/LayoutParams)
	I/MonoDroid( 6645): at Mono.Samples.Hello.HelloActivity.OnCreate (Android.OS.Bundle)
	I/MonoDroid( 6645): at Android.App.Activity.n_OnCreate_Landroid_os_Bundle_ (intptr,intptr,intptr)
	I/MonoDroid( 6645): at (wrapper dynamic-method) object.ecadbe0b-9124-445e-a498-f351075f6c89 (intptr,intptr,intptr)

Interfaces are not classes, and this is one of the places that this
is most apparent.  Because of this crash, we had to use *instance*
`jmethodID` caches:

	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    IntPtr id_addView;
	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        if (id_addView == IntPtr.Zero)
	            id_run = JNIEnv.GetMethodID (class_ref, "addView", "…");
	        JNIEnv.CallVoidMethod (Handle, id_addView, …);
	    }
	}

Pro: no more crash!

Con: *every different instance* of `IWindowManagerInvoker` needs to
separately lookup whatever methods are invoked.  There is *some*
caching, so repeated calls to `AddView()` on the same instance will
hit the cache, but if you obtain a different `IWindowManager`
instance, `jmethodID` values will need to be looked up again.

This was "fine", until xamarin/java.interop#858 enters the picture:
interface invokers were full of Android-isms --
`Android.Runtime.JNIEnv.GetMethodID()`! `JNIEnv.CallVoidMethod()`! --
and thus ***not*** APIs that @jonpryor wished to expose within
desktop Java.Base bindings.

Enter `generator --lang-features=emit-legacy-interface-invokers`:
when *not* specified, interface invokers will now use
`JniPeerMembers` for method lookup and invocation, allowing
`jmethodID` values to be cached *across* instances.  In order to
prevent the runtime crash, an interface may have *multiple*
`JniPeerMembers` values, one per implemented interface, which is used
to invoke methods from that interface.

`IWindowManagerInvoker` now becomes:

	internal partial class IWindowManagerInvoker : Java.Lang.Object, IWindowManager {
	    static readonly JniPeerMembers _members_android_view_ViewManager    = …;
	    static readonly JniPeerMembers _members_android_view_WindowManager  = …;

	    public void AddView(View view, ViewGroup.LayoutParams @params)
	    {
	        const string __id = "addView.…";
	        _members_android_view_ViewManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
	    }

	    public void RemoveViewImmediate(View view)
	    {
	        const string __id = "removeViewImmediate.…";
	        _members_android_view_WindowManager.InstanceMethods.InvokeAbstractVoidMethod (__id, this, …);
	    }
	}

This has two advantages:

 1. More caching!
 2. Desktop `Java.Base` binding can now have interface invokers.

Update `tests/generator-Tests` expected output.
Note: to keep this patch smaller, JavaInterop1 output uses the
new pattern, and only *some* XAJavaInterop1 tests use the new
pattern.

Added [CS0114][0] to `$(NoWarn)` in `Java.Base.csproj` to ignore
warnings such as:

	…/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.ICharSequence.cs(195,25): warning CS0114:
	'ICharSequenceInvoker.ToString()' hides inherited member 'Object.ToString()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

[Ignoring CS0114 is also done in `Mono.Android.dll` as well][1], so
this is not a new or unique requirement.

Update `Java.Interop.dll` so that
`JniRuntime.JniValueManager.GetActivationConstructor()` now knows
about and looks for `*Invoker` types, then uses the activation
constructor from the `*Invoker` type when the source type is an
abstract `class` or `interface`.

Update `tests/Java.Base-Tests` to test for implicit `*Invoker` lookup
and invocation support.


~~ Property Setters ~~

While testing on #8339, we hit this error
(among others, to be addressed later):

	src/Mono.Android/obj/Debug/net8.0/android-34/mcw/Android.Views.IWindowInsetsController.cs(304,41): error CS0103: The name 'behavior' does not exist in the current context

This was caused because of code such as:

	public partial interface IWindowInsetsController {
	    public unsafe int SystemBarsBehavior {
	        get {
	            const string __id = "getSystemBarsBehavior.()I";
	            try {
	                var __rm = _members_IWindowInsetsController.InstanceMethods.InvokeAbstractInt32Method (__id, this, null);
	                return __rm;
	            } finally {
	            }
	        }
	        set {
	            const string __id = "setSystemBarsBehavior.(I)V";
	            try {
	                JniArgumentValue* __args = stackalloc JniArgumentValue [1];
	                __args [0] = new JniArgumentValue (behavior);
	                _members_IWindowInsetsController.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args);
	            } finally {
	            }
	        }
	    }
	}

This happened because when emitting the property setter, we need
to update the `set*` method's parameter name to be `value` so that
the normal property setter body is emitted properly.

Update `InterfaceInvokerProperty.cs` so that the parameter name
is set to `value`.


~~ Performance ~~

What does this do for performance?

Add a new `InterfaceInvokerTiming` test fixture to
`Java.Interop-PerformanceTests.dll`, which:

 1. "Reimplements" the "legacy" and "JniPeerMembers" Invoker
    strategies
 2. For each Invoker strategy:
     a. Invokes a Java method which returns a `java.lang.Runnable`
        instance
     b. Invokes `Runnable.run()` on the instance returned by (2.a)
        …100 times.
     c. Repeat (2.a) and (2.b) 100 times.

The result is that using `JniPeerMembers` is *much* faster:

	% dotnet build tests/Java.Interop-PerformanceTests/*.csproj && \
		dotnet test --logger "console;verbosity=detailed"  bin/TestDebug-net7.0/Java.Interop-PerformanceTests.dll --filter "Name~InterfaceInvokerTiming"
	…
	 Passed InterfaceInvokers [1 s]
	 Standard Output Messages:
	## InterfaceInvokers Timing: instanceIds: 00:00:01.1095502
	## InterfaceInvokers Timing: peerMembers: 00:00:00.1400427

Using `JniPeerMembers` takes ~1/8th the time as using `jmethodID`s.

TODO: something is *probably* wrong with my test -- reviews welcome!
-- as when I increase the (2.b) iteration count, the `peerMembers`
time is largely unchanged (~0.14s), while the `instanceIds` time
increases linearly.

*Something* is wrong there.  I'm not sure what.  (Or *nothing* is
wrong, and instance `jmethodID` are just *that* bad.)


[0]: https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0114
[1]: https://github.com/xamarin/xamarin-android/blob/d5c4ec09f7658428a10bbe49c8a7a3eb2f71cb86/src/Mono.Android/Mono.Android.csproj#L12C7-L12C7
Context: 5537aec

In commit 5537aec, we updated the logic that matches a Kotlin public
property getter/setter to a generated Java getter/setter that follows
this pattern:

	// Kotlin
	public var type = 0

	// Java
	private int type = 0;

	public int getType () { ... }
	public void setType (int p0) { ... }

However, this caused unit tests in xamarin/xamarin-android to fail
that when using Kotlin unsigned types:

	KotlinUnsignedTypesTests.cs: 
	error CS0200: Property or indexer 'UnsignedInstanceMethods.UnsignedInstanceProperty' cannot be assigned to -- it is read only

This is because properties that use Kotlin unsigned types append a
`-<type-hash>` suffix to their getter/setter names:

	// Kotlin
	public var type: UInt = 0u

	// Java
	private int type = 0;

	public int getType-pVg5ArA () { ... }
	public void setType-WZ4Q5Ns (int p0) { ... }

Update our Kotlin logic to handle this case.
Context: dotnet/java-interop#1153

[JNI][0] supports *two* modes of operation:

 1. Native code creates the JVM, e.g. via [`JNI_CreateJavaVM()`][1]

 2. The JVM already exists, and when Java code calls
    [`System.loadLibrary()`][3], the JVM calls the
    [`JNI_OnLoad()`][2] function on the specified library.

Java.Interop samples and unit tests rely on the first approach,
e.g. `TestJVM` subclasses `JreRuntime`, which is responsible for
calling `JNI_CreateJavaVM()` so that Java code can be used.

PR #1153 is exploring the use of [.NET Native AOT][4] to produce a
native library which is used with Java-originated initialization.

In order to make Java-originated initialization *work*, we need
to be able to initialize `JniRuntime` and `JreRuntime` around
existing JVM-provided pointers:

  * The `JavaVM*` provided to `JNI_OnLoad()`, which can be used to
    set `JniRuntime.CreationOptions.InvocationPointer`:

        [UnmanagedCallersOnly(EntryPoint="JNI_OnLoad")]
        int JNI_OnLoad(IntPtr vm, IntPtr reserved)
        {
            var options = new JreRuntimeOptions {
                InvocationPointer = vm,
            };
            var runtime = options.CreateJreVM ();
            return runtime.JniVersion;
            return JNI_VERSION_1_6;
        }

  * The [`JNIEnv*` value provided to Java `native` methods][5] when
    they are invoked, which can be used to set
    `JniRuntime.CreationOptions.EnvironmentPointer`:

        [UnmanagedCallersOnly(EntryPoint="Java_example_Whatever_init")]
        void Whatever_init(IntPtr jnienv, IntPtr Whatever_class)
        {
            var options = new JreRuntimeOptions {
                EnvironmentPointer = jnienv,
            };
            var runtime = options.CreateJreVM ();
        }

Update `JniRuntime` and `JreRuntime` to support these Java-originated
initialization strategies.  In particular, don't require that
`JreRuntimeOptions.JvmLibraryPath` be set, avoiding:

	System.InvalidOperationException: Member `JreRuntimeOptions.JvmLibraryPath` must be set.
	   at Java.Interop.JreRuntime.CreateJreVM(JreRuntimeOptions builder)
	   at Java.Interop.JreRuntime..ctor(JreRuntimeOptions builder)
	   at Java.Interop.JreRuntimeOptions.CreateJreVM()

[0]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html
[1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#creating_the_vm
[2]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runtime.html#loadLibrary(java.lang.String)
[3]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#JNJI_OnLoad
[4]: https://learn.microsoft.com/dotnet/core/deploying/native-aot/
[5]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#native_method_arguments
Fixes: #7554

Placing `[Export]` on a constructor with parameters did not work as
expected.  Consider:

	public class Example : Java.Lang.Object
	{
	    [Export(SuperArgumentsString = "")]
	    public Example (int value)
	    {
	        this.value = value;
	    }

	    int value;
	}

This *does* generate a Java Callable Wrapper with the desired
constructor, but it *doesn't* invoke the correct C# constructor,
because of the `TypeManager.Activate()` invocation:

	// Java JCW
	/* partial */ class Example extends java.lang.Object {
	    public Example(int p0) {
	        super ();
	        if (getClass () == Example.class) {
	            mono.android.TypeManager.Activate (
	                    /* typeName */      "Namespace.Example, Assembly",
	                    /* signature */     "",
	                    /* instance */      this,
	                    /* parameterList */ new java.lang.Object[] { p0 });
	        }
	    }
	}

In particular, because `signature` is the empty string,
`TypeManager.Activate()` will lookup and invoke the *default*
constructor, rather than the `Example(int)` constructor.
(This despite the fact that `parameterList` contains arguments!)

Consequently, when Java invokes the `Example(int)` constructor,
an exception is thrown, as the default constructor it wants doesn't
exist:

	Could not activate JNI Handle 0x7ffd2bd94e50 (key_handle 0x65d856e) of Java type 'namespace/Example' as managed type 'Namespace.Example'.
	Java.Interop.JavaLocationException: Exception of type 'Java.Interop.JavaLocationException' was thrown.
	Java.Lang.Error: Exception of type 'Java.Lang.Error' was thrown.

	  --- End of managed Java.Lang.Error stack trace ---
	java.lang.Error: Java callstack:
		at mono.android.TypeManager.n_activate(Native Method)
		at mono.android.TypeManager.Activate(TypeManager.java:7)
		at namespace.Example.<init>(Example.java:0)
		…

Update `JavaCallableWrapperGenerator.AddConstructor()` so that the
`managedParameters` value is provided to the `Signature` instance for
the `[Export]` constructor.  This value is then inserted as the
`signature` parameter value, which will allow the correct constructor
to be invoked:

	// Fixed Java JCW
	/* partial */ class Example extends java.lang.Object {
	    public Example(int p0) {
	        super ();
	        if (getClass () == Example.class) {
	            mono.android.TypeManager.Activate (
	                    /* typeName */      "Namespace.Example, Assembly",
	                    /* signature */     "System.Int32, System.Runtime",
	                    /* instance */      this,
	                    /* parameterList */ new java.lang.Object[] { p0 });
	        }
	    }
	}
Context: dotnet/java-interop#1153
Context: 58f41b8
Context: 16cd04f

PR #1153 is exploring the use of [.NET Native AOT][0] to produce a
native library which is used *within* a `java`-originated process:

	% java -cp … com/microsoft/hello_from_jni/App
	# launches NativeAOT-generated native lib, executes C# code…

As NativeAOT has no support for `System.Reflection.Emit`, the only
way for Java code to invoke managed code -- in a Desktop Java.Base
environment! [^0] see 58f41b8 -- would be to pre-generate the
required marshal methods via `jnimarshalmethod-gen`.

This in turn requires updating `jcw-gen` to support the pre-existing
`Java.Interop.JavaCallableAttribute`, so that C# code could
reasonably declare methods visible to Java, along with the
introduction of, and support for, a new
`Java.Interop.JavaCallableConstructorAttribute` type.  This allows
straightforward usage:

	[JniTypeSignature ("example/ManagedType")]      // for a nice Java name!
	class ManagedType : Java.Lang.Object {

	    int value;

	    [JavaCallableConstructor(SuperConstructorExpression="")]
	    public ManagedType (int value) {
	        this.value = value;
	    }

	    [JavaCallable ("getString")]
	    public Java.Lang.String GetString () {
	        return new Java.Lang.String ($"Hello from C#, via Java.Interop! Value={value}");
	    }
	}

Run this through `jcw-gen` and `jnimarshalmethod-gen`, run the app,
and nothing worked (?!), because not all pieces were in agreement.

Java `native` method registration is One Of Those Things™ that
involves lots of moving pieces:

  * `generator` emits bindings for Java types, which includes Java
    method names, signatures, and (on .NET Android) the "connector
    method" to use:

        [Register ("toString", "()Ljava/lang/String;", "GetToStringHandler")]   // .NET Android
        [JniMethodSignature ("toString", "()Ljava/lang/String;")]               // Java.Base
        public override unsafe string? ToString () {…}

  * `jcw-gen` uses `generator` output, *prefixing* Java method names
    with `n_` for `native` method declarations, along with a
    method wrapper [^1]

        public String toString() {return n_toString();}
        private native String n_toString();

  * `jnimarshalmethod-gen` emits marshal methods for Java.Base,
    and needs to register the `native` methods declared by `jcw-gen`.
    `jnimarshalmethod-gen` and `jcw-gen` need to be consistent with
    each other.

  * `MarshalMemberbuilder.CreateMarshalToManagedMethodRegistration()`
    creates a `JniNativeMethodRegistration` instance which contains
    the name of the Java `native` method to register, and was
    using a name inconsistent with `jcw-gen`.

Turns Out, `jcw-gen`, `jnimarshalmethod-gen`, and
`MarshalMemberBuilder` were *not* consistent.

The only "real" `jnimarshalmethod-gen` usage (16cd04f) is with the
`Java.Interop.Export-Tests` unit test assembly, which *did not use*
`jcw-gen`; it contained only hand-written Java code.  Consequently,
*none* of the Java `native` methods declared within it had an `n_`
prefix, and since this worked with `jnimarshalmethod-gen`, this means
that `jnimarshalmethod-gen` registration logic likewise didn't use
`n_` prefixed method names.

The result is that in the NativeAOT app, it would attempt to register
the `native` Java method `ManagedType.getString()`, while what
`jcw-gen` declared was `ManagedType.n_getString()`!

Java promptly threw an exception, and the app crashed.

Update `Java.Interop.Export-Tests` so that all the methods used with
`MarshalMemberBuilder` are declared with `n_` prefixes, and add
a `Java.Lang.Object` subclass example to the unit tests:

Update `tests/Java.Interop.Tools.JavaCallableWrappers-Tests` to
add a test for `.CodeGenerationTarget==JavaInterop1`.

Add `$(NoWarn)` to
`Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` in order to
"work around" warnings-as-errors:

	…/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(19,63): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name
	…/src/Java.Interop.NamingCustomAttributes/Android.Runtime/RegisterAttribute.cs(53,4): error CA1019: Remove the property setter from Name or reduce its accessibility because it corresponds to positional argument name
	…/src/Java.Interop.NamingCustomAttributes/Java.Interop/ExportFieldAttribute.cs(12,16): error CA1813: Avoid unsealed attributes
	…

These are "weird"; the warnings/errors appear to come in because
`Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` now includes:

	<Compile Include="..\..\src\Java.Interop\Java.Interop\JniTypeSignatureAttribute.cs" />

which appears to pull in `src/Java.Interop/.editorconfig`, which makes
CA1019 and CA1813 errors.  (I do not understand what is happening.)

Update `jnimarshalmethod-gen` so that the Java `native` methods it
registers have an `n_` prefix.

Refactor `ExpressionAssemblyBuilder.CreateRegistrationMethod()` to
`ExpressionAssemblyBuilder.AddRegistrationMethod()`, so that
the `EmitConsoleWriteLine()` invocation can provide the *full*
type name of the `__RegisterNativeMembers()` method, which helps
when there is more than one such method running around…

Update `ExpressionAssemblyBuilder` so that the delegate types it
creates for marshal method registration all have
`[UnmanagedFunctionPointer(CallingConvention.Winapi)]`.  (This isn't
needed *here*, but is needed in the context of NativeAOT, as
NativeAOT will only emit "marshal stubs" for delegate types which
have `[UnmanagedFunctionPointer]`.)  Unfortunately, adding
`[UnmanagedFunctionPointer]` broke things:

	error JM4006: jnimarshalmethod-gen: Unable to process assembly '…/Hello-NativeAOTFromJNI.dll'
	Failed to resolve System.Runtime.InteropServices.CallingConvention
	Mono.Cecil.ResolutionException: Failed to resolve System.Runtime.InteropServices.CallingConvention
	   at Mono.Cecil.Mixin.CheckedResolve(TypeReference self)
	   at Mono.Cecil.SignatureWriter.WriteCustomAttributeEnumValue(TypeReference enum_type, Object value)
	   …

The problem is that `CallingConvention` was resolved from
`System.Private.CoreLib`, and when we removed that assembly
reference, the `CallingConvention` couldn't be resolved at all.

We could "fix" this by explicitly adding a reference to
`System.Runtime.InteropServices.dll`, but how many more such corner
cases exist?  The current approach is not viable.

Remove the code from 16cd04f which attempts to remove
`System.Private.CoreLib`.  So long as `ExpressionAssemblyBuilder`
output is *only* used in "completed" apps (not distributed in NuGet
packages or some "intermediate" form), referencing
`System.Private.CoreLib` is "fine".

Update `jnimarshalmethod-gen` assembly location probing: in #1153, it
was attempting to resolve the *full assembly name* of `Java.Base`, as
`Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null`,
causing it to attempt to load the file
`Java.Base, Version=7.0.0.0, Culture=neutral, PublicKeyToken=null.dll`,
which doesn't exist.  Use `AssemblyName` to parse the string and
extract out the assembly name, so that `Java.Base.dll` is probed for
and found.

Update `JreTypeManager` to *also* register the marshal methods
generated by
`Runtime.MarshalMemberBuilder.GetExportedMemberRegistrations()`.

With all that, the updated `Java.Interop.Export-Tests` test now work
both before and after `jnimarshalmethod-gen` is run:

	% dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll &&
	  dotnet bin/Debug-net7.0/jnimarshalmethod-gen.dll  bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll &&
	  dotnet test --logger "console;verbosity=detailed" bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll
	…

TODO:

  * `generator --codegen-target=JavaInterop1` should emit
    JNI method signature information for constructors!
    This would likely remove the need for
    `[JavaCallableConstructor(SuperConstructorExpression="")`.

  * dotnet/java-interop#1159

[0]: https://learn.microsoft.com/dotnet/core/deploying/native-aot

[^0]: In a .NET Android environment, marshal methods are part of
      `generator` output, so things would be more straightforward
      there, though all the `_JniMarshal_*` types that are declared
      would also need to have
      `[UnmanagedFunctionPointer(CallingConvention.Winapi)]`…

[^1]: Why not just declare `toString()` as `native`?  Why have the
      separate `n_`-prefixed version?  To make the
      `#if MONODROID_TIMING` block more consistent within
      `JavaCallableWrapperGenerator.GenerateMethod()`.
      It's likely "too late" to *easily* change this now.
Context: e75741e
Context: https://xamarin.github.io/bugzilla-archives/23/2367/bug.html
Context: https://github.com/xamarin/monodroid/commit/6679dfc62eae462b5acc9deb095d0fa41786f80b

Constructors, how do they work?

Commit e75741e goes into some details about the interaction between
Java constructors and managed-side constructors when the Java
constructor is invoked first.

What happens when the managed-side constructor is invoked first?

	class JavaInteropExample : Java.Lang.Object {
	    [JavaCallableConstructor(SuperConstructorExpression="")]
	    public JavaInteropExample (int a, int b) {}
	}

*Before* that code runs (ideally), `jcw-gen` (or equivalent) will
run, creating a Java Callable Wrapper for `JavaInteropExample`,
and that Java Callable Wrapper (JCW) will contain a constructor:

	// JCW
	/* partial */ class JavaInteropExample extends java.lang.Object {
	    public JavaInteropExample(int p0, int p1) {
	        super ();
	        if (getClass () == JavaInteropExample.class) {
	            ManagedPeer.construct (…);
	        }
	    }
	}

Then someone tries:

	// C#
	var o = new JavaInteropExample(42);

What happens is:

 1. `JavaInteropExample(int, int)` constructor begins execution,
    *immediately* executes (implicit) base constructor invocation
    `: base()`.

 2. `Java.Lang.Object` default constructor executes, which invokes
    `JavaObject(ref JniObjectReference, JniObjectReferenceOptions)`
    constructor, which is a no-op as the `JniObjectReference` is invalid.

 3. `Java.Lang.Object` default constructor continues, hitting:

        var peer = JniPeerMembers.InstanceMethods.StartCreateInstance ("()V", GetType (), null);

    which looks up the Java peer, and invokes the default constructor
    on the Java peer.

 4. `JavaObject.PeerReference` (eventually) is `peer.NewGlobalRef()`,
    and the `JavaInteropExample` constructor can *now* begin executing.

If the JCW doesn't contain a default constructor, then things fail:

	Error Message:
	 Java.Interop.JavaException : Lnet/dot/jni/test/JavaCallableExample;.<init>()V
	Stack Trace:
	   at Java.Interop.JniEnvironment.InstanceMethods.GetMethodID(JniObjectReference type, String name, String signature) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/obj/Debug/net7.0/JniEnvironment.g.cs:line 19947
	 at Java.Interop.JniType.GetConstructor(String signature) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniType.cs:line 182
	 at Java.Interop.JniPeerMembers.JniInstanceMethods.GetConstructor(String signature) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 63
	 at Java.Interop.JniPeerMembers.JniInstanceMethods.FinishCreateInstance(String constructorSignature, IJavaPeerable self, JniArgumentValue* parameters) in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:line 173
	 at Java.Lang.Object..ctor() in /Users/jon/Developer/src/xamarin/java.interop/src/Java.Base/obj/Debug-net7.0/mcw/Java.Lang.Object.cs:line 33
	 at Java.InteropTests.JavaCallableExample..ctor(Int32 a) in /Users/jon/Developer/src/xamarin/java.interop/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExample.cs:line 11
	 at Java.InteropTests.JavaCallableExampleTest.ManagedCtorInvokesJavaDefaultCtor() in /Users/jon/Developer/src/xamarin/java.interop/tests/Java.Interop.Export-Tests/Java.Interop/JavaCallableExampleTests.cs:line 22
	 at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
	 at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
	--- End of managed Java.Interop.JavaException stack trace ---
	java.lang.NoSuchMethodError: Lnet/dot/jni/test/JavaCallableExample;.<init>()V

But why would the JCW for `JavaInteropExample` contain a default
constructor at all?

In .NET Android, bound constructors have `[Register]`, and `jcw-gen`
will emit constructors based on "visible" `[Register]`ed constructors.
This ensures that we get *at least one* constructor that exists in
Java, which the C# derived types will need to invoke.

`generator --codegen-target=JavaInterop1`-style bindings didn't
previously emit anything for bound constructors, preventing `jcw-gen`
from performing this same logic.  Consequently, the JCW for
`JavaInteropExample` *didn't* have a default constructor.

Add a new `Java.Interop.JniConstructorSignatureAttribute` type, and
update `generator` to emit this attribute on bound constructors.

Update `jcw-gen` to support `JniConstructorSignatureAttribute`.

This adds a default constructor to `JavaInteropExample`:

	// JCW
	/* partial */ class JavaInteropExample extends java.lang.Object {
	    public JavaInteropExample() {
	        super ();
	        if (getClass () == JavaInteropExample.class) {
	            ManagedPeer.construct (…);
	        }
	    }

	    public JavaInteropExample(int p0, int p1) {
	        super ();
	        if (getClass () == JavaInteropExample.class) {
	            ManagedPeer.construct (…);
	        }
	    }
	}

Note: while there is a public default constructor on the JCW, Java
code cannot call it.  If it attempts to do so, an exception will
be thrown because the C#-side type doesn't have a default constructor.
Changes: dotnet/android-tools@8a971d9...8d38281

  * dotnet/android-tools@8d38281: Update the maximum NDK version to 26 (dotnet/android-tools#219)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Fixes: dotnet/java-interop#1159

Context: c6faab7

`jnimarshalmethod-gen` is intended to generate marshal methods for
any type that that:

  * Has `[JavaCallable]` (tested in `Java.Interop.Export-Tests.dll`
    and c6faab7), or
  * Overrides a `virtual` method which has `[JniMethodSignature]`, or
  * Implements an interface method which has `[JniMethodSignature]`.

Thus, the intention was that it should generate marshal methods for
`Java.Base-Tests`:

	% dotnet bin/Debug-net7.0/jnimarshalmethod-gen.dll -v bin/TestDebug-net7.0/Java.Base-Tests.dll
	Unable to read assembly 'bin/TestDebug-net7.0/Java.Base-Tests.dll' with symbols. Retrying to load it without them.
	Preparing marshal method assembly 'Java.Base-Tests-JniMarshalMethods'
	Processing Java.BaseTests.JavaInvoker type
	Processing Java.BaseTests.MyRunnable type
	Processing Java.BaseTests.MyIntConsumer type
	Marshal method assembly 'Java.Base-Tests-JniMarshalMethods' created

Notably missing? No messages stating:

	Adding marshal method for …

Also missing?  `ikdasm bin/TestDebug-net7.0/Java.Base-Tests.dll`
showed that there were no `__RegisterNativeMembers()` methods emitted.

The `jnimarshalmethod-gen` invocation was a no-op!

The problems were twofold:

 1. It was only looking for methods with
    `Android.Runtime.RegisterAttribute`.  This was useful for
    Xamarin.Android (when we were trying to make it work), but
    doesn't work with Java.Base.  We need to *also* look for
    `Java.Interop.JniMethodSignature`.

    Relatedly, the attempt to use
    `registerAttribute.Constructor.Parameters` to determine parameter
    names didn't work; the parameter name was always `""`.

 2. A'la c6faab7, we need to ensure that the Java `native` methods
    we register are consistent with `jcw-gen` output.

Fix these two problems, which allows `jnimarshalmethod-gen` to now
emit marshal methods for types within `Java.Base-Tests.dll`.

Additionally, rework the `jnimarshalmethod-gen -f` logic to *remove*
the existing `__<$>_jni_marshal_methods` nested type when present.
The eventual plan is to move the xamarin/Java.Interop repo to
dotnet/jni (timeline: unspecified), and existing Java code within
dotnet/runtime (for use with .NET Android) already uses a
`net.dot` package-name prefix.

As "xamarin" is increasingly "persona-non-grata", *and* these types
aren't actually used publicly -- .NET Android née Xamarin.Android
has it's own set of `mono.android` types -- we can safely rename
the `com.xamarin.java_interop` and related packages to instead use
a `net.dot.jni` prefix.
Context: a96095f
Context: cf60cef

Commit cf60cef added bindings for `java.util.function`.

Commit a96095f added `[JniConstructorSignature]` to bound
constructors.

Update `src/Java.Base-ref.cs` to contain these changes, so that
`git diff` doesn't constantly show this file as having changed.
…1170)

Fixes: dotnet/java-interop#1169

Context: 76ab8b2

76ab8b2c mentions:

> Now that we are in the .NET `TargetFramework` world we also need
> to ensure we do not *add* any new API to a Target Framework once it
> has shipped.  A `TargetFramework` is essentially a contract that we
> cannot change.  (Imagine if you had different minor versions of .NET
> on your local machine and CI machine, what works on one should work
> on the other.)

This prevents an issue where a user on `.NET 8.0.300` uses a method
that isn't available to their coworker or CI on `.NET 8.0.100`.

This logical argument also applies to `Java.Interop.dll`.

Enable Microsoft's [PublicApiAnalyzers][0] for `Java.Interop.dll`.
([PublicApiAnalyzers documentation][1].)  This ensures that we don't
add new API once we've shipped `Java.Interop.dll` for a given
.NET version.

Update `build-tools/jnienv-gen` so that `JniEnvironment.g.cs` enables
nullable reference types.  This allows us to *avoid* disabling RS0041.

Co-authored-by: Jonathan Pryor <jonpryor@vt.edu>

[0]: https://github.com/dotnet/roslyn-analyzers/tree/ace28a1039d09626a94d3b0f8ce69e547ca2bcbf/src/PublicApiAnalyzers
[1]: https://github.com/dotnet/roslyn-analyzers/blob/ace28a1039d09626a94d3b0f8ce69e547ca2bcbf/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md
Fixes: dotnet/java-interop#1165

Context: dotnet/java-interop#1153
Context: dotnet/java-interop#1157
Context: 522ce71

When building for NativeAOT (#1153) or when building .NET Android
apps with `-p:IsAotcompatible=true` (#1157), we get [IL2057][0]
warnings from `ManagedPeer.cs`:

	ManagedPeer.cs(93,19,93,112): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type.
	ManagedPeer.cs(156,18,156,65): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type.
	ManagedPeer.cs(198,35,198,92): warning IL2057: Unrecognized value passed to the parameter 'typeName' of method 'System.Type.GetType(String, Boolean)'. It's not possible to guarantee the availability of the target type.

These warnings are because `ManagedPeer.Construct()` and
`ManagedPeer.RegisterNativeMembers()` use `Type.GetType()` on string
values provided *from Java code*, and thus the IL trimmer does not
have visibility into those strings, and thus cannot reliably
determine which types need to be preserved:

	// Java Callable Wrapper
	/* partial */ class ManagedType
	{
	  public static final String __md_methods;
	  static {
	    __md_methods =
	      "n_GetString:()Ljava/lang/String;:__export__\n" +
	      "";
	    net.dot.jni.ManagedPeer.registerNativeMembers (
	        /* nativeClass */             ManagedType.class,
	        /* assemblyQualifiedName */   "Example.ManagedType, Hello-NativeAOTFromJNI",
	        /* methods */                 __md_methods);
	  }

	  public ManagedType (int p0)
	  {
	    super ();
	    if (getClass () == ManagedType.class) {
	      net.dot.jni.ManagedPeer.construct (
	          /* self */                  this,
	          /* assemblyQualifiedName */ "Example.ManagedType, Hello-NativeAOTFromJNI",
	          /* constructorSignature */  "System.Int32, System.Runtime",
	          /* arguments */             new java.lang.Object[] { p0 });
	    }
	  }
	}

`ManagedPeer.construct()` passes *two* sets of assembly-qualified
type names: `assemblyQualifiedName` contains the type to construct,
while `constructorSignature` contains a `:`-separated list of
assembly-qualified type names for the constructor parameters.
Each of these are passed to `Type.GetType()`.

`ManagedPeer.registerNativeMembers()` passes an assembly-qualified
type name to `ManagedPeer.RegisterNativeMembers()`, which passes the
assembly-qualified type name to `Type.GetType()` to find the type
to register native methods for.

If we more strongly rely on JNI signatures, we can remove the need
for Java Callable Wrappers to contain assembly-qualified type names
entirely, thus removing the need for `ManagedPeer` to use
`Type.GetType()`, removing the IL2057 warnings.

For `ManagedPeer.construct()`, `assemblyQualifiedName` can be
replaced with getting the JNI type signature from `self.getClass()`,
and `constructorSignature` can be replaced with a
*JNI method signature* of the calling constructor.

For `ManagedPeer.registerNativeMembers()`, `assemblyQualifiedName`
can be replaced with getting the JNI type signature from `nativeClass`.
`jcw-gen --codegen-target=JavaInterop1` output becomes:

	// New JavaInterop1 Java Callable Wrapper
	/* partial */ class ManagedType
	{
	  public static final String __md_methods;
	  static {
	    __md_methods =
	      "n_GetString:()Ljava/lang/String;:__export__\n" +
	      "";
	    net.dot.jni.ManagedPeer.registerNativeMembers (
	        /* nativeClass */             ManagedType.class,
	        /* methods */                 __md_methods);
	  }

	  public ManagedType (int p0)
	  {
	    super ();
	    if (getClass () == ManagedType.class) {
	      net.dot.jni.ManagedPeer.construct (
	          /* self */                  this,
	          /* constructorSignature */  "(I)V",
	          /* arguments */             new java.lang.Object[] { p0 });
	    }
	  }
	}

This does not alter `jcw-gen --codegen-target=XAJavaInterop1` output;
.NET Android will continue to require `Type.GetType()` calls within
xamarin/xamarin-android, e.g.
[`AndroidTypeManager.RegisterNativeMembers()`][2].

Furthermore, if we add `[DynamicallyAccessedMembers]` to
`JniRuntime.JniTypeManager.GetType()`, we can fix some [IL2075][1]
warnings which appeared after fixing the IL2057 warnings.

Aside: Excising assembly-qualified type names from Java Callable
Wrappers had some "interesting" knock-on effects in the unit tests,
requiring that more typemap information be explicitly provided.
(This same information was *implicitly* provided before, via the
provision of assembly-qualified type names everywhere…)

One problem with the approach of using JNI signatures instead of
using assembly-qualified names is *ambiguity*: there can be multiple
managed types which correspond to a given JNI signature.  Consider
the JNI signature `[I`, which is a Java `int[]`.  This is bound as:

  * C# `int[]`
  * `JavaArray<int>`
  * `JavaPrimitiveArray<int>`
  * `JavaInt32Array`

How do we know which to use?  Using assembly-qualified type names
for constructor parameters nicely solved this issue, but if we're not
using them anymore…

Update `JavaCallableExample` to demonstrate this:

	partial class JavaCallableExample {
	    [JavaCallableConstructor(SuperConstructorExpression="")]
	    public JavaCallableExample (int[] a, JavaInt32Array b);
	}

The intention is twofold:

 1. This should result in a Java Callable Wrapper constructor with
    signature `JavaCallableExample(int[] p0, int[] p1)`, and

 2. Java code should be able to invoke this constructor.

Turns out, neither of these worked when `Type.GetType()` is not used
for constructor argument lookup: `JavaCallableWrapperGenerator`
didn't fully support e.g. `[JniTypeSignature("I", ArrayRank=1)]`
(present on `JavaInt32Array`), so it didn't know what to do with
the `JavaInt32Array` parameter.

Once (1) was fixed, (2) would fail because
`JniRuntime.JniTypeManager.GetType(JniTypeSignature.Parse("[I"))`
would return `JavaPrimitiveArray<int>`, which wasn't used in
`JavaCallableExample`, resulting in:

	System.NotSupportedException : Unable to find constructor
	  Java.InteropTests.JavaCallableExample(Java.Interop.JavaPrimitiveArray`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Java.Interop.JavaPrimitiveArray`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]).
	  Please provide the missing constructor.
	  ----> Java.Interop.JniLocationException : Exception of type 'Java.Interop.JniLocationException' was thrown.
	  Stack Trace:
	     at Java.Interop.ManagedPeer.GetConstructor(JniTypeManager typeManager, Type type, String signature, Type[]& parameterTypes)
	   at Java.Interop.ManagedPeer.Construct(IntPtr jnienv, IntPtr klass, IntPtr n_self, IntPtr n_constructorSignature, IntPtr n_constructorArguments)
	…
	  --- End of managed Java.Interop.JavaException stack trace ---
	java.lang.Throwable
		at net.dot.jni.ManagedPeer.construct(Native Method)
		at net.dot.jni.test.JavaCallableExample.<init>(JavaCallableExample.java:32)
		at net.dot.jni.test.UseJavaCallableExample.test(UseJavaCallableExample.java:8)

The constructor couldn't be found because
`JniRuntime.JniTypeManager.GetTypes()` was incomplete, which is
a longstanding limitation from 522ce71: for `[I`, it would only
return `JavaPrimitiveArray<int>` and `int[]`, in that order.

Fix both of these.
`JniRuntime.JniTypeManager.GetTypes(JniTypeSignature.Parse("[I"))`
will now include:

  * `JavaArray<int>`
  * `JavaPrimitiveArray<int>`
  * `JavaInt32Array`
  * `int[]`

This now allows the `JavaCallableExample` constructor to be invoked
from Java.

Because `ManagedPeer.Construct()` is now doing so much extra work
in order to find the `ConstructorInfo` to invoke, cache the lookups.
(Technically this is a "memory leak," as cache entries are never
removed.)

Finally, update `CecilCompilerExpressionVisitor` to emit `newobj`
in certain `VisitNew()` invocations.  This was needed while trying:

	partial class JavaCallableExample {
	    [JavaCallable ("getA")]
	    public int[] GetA() => this.a;
	}

in order to fix the IL error:

	% $HOME/.dotnet/tools/ilverify bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll \
	    --tokens --system-module System.Private.CoreLib \
	    -r 'bin/TestDebug-net7.0/*.dll' \
	    -r '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.10/*.dll'
	[IL]: Error [StackUnderflow]: […/bin/TestDebug-net7.0/Java.Interop.Export-Tests.dll : .__<$>_jni_marshal_methods::n_GetA(native int, native int)][offset 0x0000002F] Stack underflow.

Unfortunately, even after the above fix invalid IL was generated during
`jnimarshalmethod-gen` processing, which will be investigated later.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/IL2057
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-warnings/il2075
[2]: https://github.com/xamarin/xamarin-android/blob/main/src/Mono.Android/Android.Runtime/AndroidRuntime.cs#L481-L577
Changes: dotnet/android-tools@8d38281...4889bf0

  * dotnet/android-tools@4889bf0: [MSBuildReferences.projitems] Require opt-in to use `Microsoft.Build` (dotnet/android-tools#220)
  * dotnet/android-tools@21de3d7: [build] update $(MSBuildPackageReferenceVersion) to 17.6.3 (dotnet/android-tools#221)
  * dotnet/android-tools@08a6990: Bump android-sdk NDK version to 26.1.10909125
  * dotnet/android-tools@6ae1f2a: Bump android-sdk build-tool version to 34.0.0
  * dotnet/android-tools@184b6b3: Bump android-sdk cmdline-tools to version 11.0
  * dotnet/android-tools@1365e33: Bump android-sdk platforms-tools to version 34.0.5

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Building the `Java.Interop.sln` solution always produces some generated
files that `git` wants to try to commit:

	Changes not staged for commit:
	  (use "git add <file>..." to update what will be committed)
	  (use "git restore <file>..." to discard changes in working directory)
	        modified:   tests/invocation-overhead/jni.cs

	Untracked files:
	  (use "git add <file>..." to include in what will be committed)
	        tests/invocation-overhead/jni-api.h
	        tests/invocation-overhead/jni.c

Commit these generated files, and update the generator for `jni.cs` to
always use native platform new lines so it does not show up as modified
on Windows.
Context: https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/25351/APIScan-step-by-step-guide-to-setting-up-a-Pipeline

The ApiScan task has been added to pipeline runs against `main`.  This
task should help us identify related issues earlier, rather than having
to wait for a full scan of VS.
Changes: dotnet/android-tools@4889bf0...ed102fc

  * dotnet/android-tools@ed102fc: [Xamarin.Android.Tools.Versions] Add JavaSdkVersion (#226)
  * dotnet/android-tools@b175674: [ci] Only enable CodeQL on Windows build job (#224)
  * dotnet/android-tools@2a2e64b: [ci] Add API Scan job (#225)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
jonathanpeppers and others added 4 commits June 26, 2026 08:33
The two `api-24.xml.in` files were ~28 MiB each (56 MiB total) and
nearly identical: only 5 `annotated-visibility="TESTS"` markers (used
by `JavaTypeModelsTests.AnnotatedVisibility`) differed.

Consolidate them into a single shared file under `tests/TestData/` and
strip it down to ~390 KB while keeping all 30 tests (17 in
`Java.Interop.Tools.JavaTypeSystem-Tests` + 13 in
`Xamarin.Android.Tools.ApiXmlAdjuster-Tests`) passing.

Changes:
* Move `Java.Interop.Tools.JavaTypeSystem-Tests/api-24.xml.in` to
  `tests/TestData/api-24.xml.in` (preserves the TESTS annotations).
* Delete the duplicate copy under
  `Xamarin.Android.Tools.ApiXmlAdjuster-Tests`.
* Point both `JavaApiTestHelper.cs` files at the shared path.
* `Xamarin.Android.Tools.ApiXmlAdjuster`'s loader does not recognize
  `annotated-visibility`; strip the attribute in-memory before parsing
  in that test project's helper.
* Trim the shared XML to the transitive type-resolution closure needed
  by the test assertions (685 types out of 3823), and additionally strip
  method/field/constructor bodies from types whose bodies aren't
  inspected by any test, keeping bodies only on a small allowlist
  (`ContentObservable`, `Observable`, `ConcurrentHashMap`,
  `Activity`, `StateListAnimator`,
  `DrmStore.ConstraintsColumns`).

Net repo size reduction: ~55.6 MiB.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary

Register JNI native methods by marshalling into the **blittable** `JniNativeMethod` struct and calling the existing `RegisterNatives(JniObjectReference, ReadOnlySpan<JniNativeMethod>)` overload, instead of invoking the JNI `RegisterNatives` function pointer with a **non-blittable managed array** (`JniNativeMethodRegistration[]`).

This avoids relying on the runtime to marshal an array of a non-blittable struct across a `delegate* unmanaged<>` call — a path that is currently miscompiled by crossgen2 under composite ReadyToRun + PGO and corrupts the registered method names.

Refs #11633

## Background: what breaks

The default (non-trimmable / llvm-ir typemap) registration funnels through:


```csharp
// JniEnvironment.Types
public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods)
    => _RegisterNatives (type, methods, numMethods);   // generated invoker:
// delegate* unmanaged<IntPtr, jobject, JniNativeMethodRegistration[], int, int> RegisterNatives
```

`JniNativeMethodRegistration` is non-blittable (`string Name; string Signature; Delegate Marshaler`). Passing `JniNativeMethodRegistration[]` through the `delegate* unmanaged<>` requires the runtime to marshal the array element-by-element.

`ManagedPeer..cctor`, `AndroidTypeManager`, `ManagedTypeManager`, and every `JniType.RegisterNativeMethods` caller funnel through this single method (`_RegisterNatives` has exactly one caller), so fixing it here fixes all of them.

## Root cause (a crossgen2 / runtime regression)

dotnet/runtime **#126911** ("Move built-in array marshalling to managed", 2026-05-01) moved array-of-struct marshalling from native C++ into the managed generic `System.StubHelpers.StructureMarshaler<T> : IArrayElementMarshaler<T, StructureMarshaler<T>>`. Its element converter is an intrinsic with a **blittable-only fallback body**:


```csharp
// src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
// "Non-blittable structs should have a custom IL body generated with the marshaling logic."
[Intrinsic]
private static void ConvertToUnmanagedCore (ref T managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList)
    => SpanHelpers.Memmove (ref *unmanaged, ref Unsafe.As<T,byte>(ref managed), (nuint)sizeof(T));
```

For non-blittable `T`, the VM generates a real per-field marshalling body at JIT time (`StructMarshalStubs::TryGenerateStructMarshallingMethod`, `dllimport.cpp`). **crossgen2 has no equivalent**, so when PGO/MIBC marks the marshaller hot and crossgen2 precompiles the shared canonical (`__Canon`) instantiation, it emits the literal `Memmove` fallback — a raw blit of the managed object references into the native struct.

Disassembly of the precompiled canonical converter from a composite-R2R + MIBC image:


```asm
StructureMarshaler`1<__Canon>.ConvertToUnmanagedCore:
    ldr  x0, [x1]     ; first 8 bytes of the managed struct = the Name string REFERENCE
    str  x0, [x2]     ; stored straight into the native JNINativeMethod.name  ← no string→char* marshalling
    ret
```

So JNI receives a managed `string` object pointer where it expects a UTF-8 `char*`, the method name is garbage, and registration fails with `NoSuchMethodError` during startup (e.g. `MauiApplication`, `net.dot.jni.ManagedPeer`).

This only manifests when crossgen2 precompiles the marshaller (MIBC marks it hot). Without a profile the JIT compiles it and registration is correct — which is why the same app works without a startup MIBC.

## Why this is the right fix

- It **eliminates the non-blittable array marshalling** at this call site entirely. The blittable `RegisterNatives(ReadOnlySpan<JniNativeMethod>)` path marshals names/signatures to UTF-8 ourselves (`Marshal.StringToCoTaskMemUTF8`) and passes a blittable `JniNativeMethod*` — there is no `StructureMarshaler<T>` involved, so it is correct regardless of the crossgen2 bug.
- It matches the **trimmable type-map path**, which already used the blittable overload and was therefore never affected.
- It is the single registration chokepoint, so it fixes `ManagedPeer`, `AndroidTypeManager`, and `ManagedTypeManager` together.

We do **not** want to marshal arrays of non-blittable types across `delegate* unmanaged<>` / P/Invoke boundaries given this runtime limitation. A scan of the shipped runtime path (Java.Interop + Mono.Android) shows `RegisterNatives` was the only such site: it is the only invoker function pointer with an array parameter, the only non-blittable-array native call; all `JValue[]` call sites already pin (`fixed (JValue* …)`) and pass a blittable pointer.

## Changes

- Rework `JniEnvironment.Types.RegisterNatives(JniObjectReference, JniNativeMethodRegistration[], int)` to marshal `Name`/`Signature` to unmanaged UTF-8 and a function pointer, then dispatch to the blittable `RegisterNatives(JniObjectReference, ReadOnlySpan<JniNativeMethod>)` overload (no more `_RegisterNatives` / non-blittable `delegate* unmanaged<>` call).
- Preserve JNI error behavior in the blittable overload: it now observes and rethrows (clearing) any pending Java exception from `JNIEnv::RegisterNatives()` — e.g. `NoSuchMethodError` — and guards `type.IsValid`, matching the generated `_RegisterNatives` wrapper it replaces. This benefits both the array-based path and the trimmable type-map path (which previously could leave a pending exception in the JNIEnv). 
- Add regression tests for the `JniNativeMethodRegistration[]` path to validate correct UTF-8 marshaling and function pointer conversion, covering both stack-allocated (≤32 methods) and heap-allocated (>32 methods) code paths.

## Implementation notes

- UTF-8 name/signature buffers are allocated with `Marshal.StringToCoTaskMemUTF8` and freed in a `finally` block with `Marshal.ZeroFreeCoTaskMemUTF8`, guarding against `IntPtr.Zero` to prevent crashes with uninitialized entries. `GC.KeepAlive (methods)` keeps the marshaler delegates alive across the native call.
- Null `Marshaler` delegates are explicitly checked with an actionable error message that includes the array index and method signature, rather than relying on the generic `ArgumentNullException` from `Marshal.GetFunctionPointerForDelegate`.
- `Marshal.GetFunctionPointerForDelegate` is called inline within the already-`RequiresDynamicCode`-annotated method. This `JniNativeMethodRegistration[]` path runs only on JIT-capable runtimes (MonoVM/CoreCLR); NativeAOT registers through the trimmable type map with statically-compiled function pointers and never reaches it.

## Verification

Reproduced and fixed in an isolated `net11.0` console app (no Java.Interop/Android types): a non-blittable `struct[]` passed through a `delegate* unmanaged<>` to a real native function corrupts only under composite R2R + MIBC (when the call site is hot); the blittable equivalent is correct under all configurations (JIT, plain R2R, composite R2R, composite R2R + MIBC).

## Tracking

- Underlying runtime regression introduced by dotnet/runtime#126911; durable fix belongs in crossgen2 (expand the marshalling intrinsic for non-blittable `__Canon`, or defer it to the JIT). This PR is the Java.Interop-side fix and is correct independent of the runtime change.
#1483)

Fixes #1335.

## Why

[JSpecify](https://jspecify.dev/) is becoming the standard way Java libraries (AndroidX, Guava, etc.) declare nullness. Unlike the older `@NonNull`/`@Nullable` declaration annotations we already recognize, JSpecify uses `TYPE_USE` annotations plus a scope-default model (`@NullMarked` on a package or class means "every reference here is non-null unless marked `@Nullable`"). The bytecode parser was missing two pieces required to consume any of this: it never read JSR 308 type annotations, and it had no notion of a scope default. As a result, generated bindings for JSpecify-annotated Java libraries were emitting reference types without `not-null="true"`, which downstream becomes nullable C# references.

## Approach

This change adds the minimum useful slice so that most APIs in a JSpecify-annotated library get correct C# nullable reference type annotations:

- **JSR 308 type-annotation parsing.** New `TypeAnnotation` type and `RuntimeVisible/InvisibleTypeAnnotationsAttribute` classes that fully parse the `target_info` discriminated union (every variant is read past, even ones we don't act on yet, so the stream stays aligned) and skip past `type_path` while remembering its length.
- **Scope detection.** `ClassPath` now retains `package-info.class` files and exposes `GetPackageInfo(packageName)`. `XmlClassDeclarationBuilder` computes `isNullMarked` from the class's own `@NullMarked`/`@NullUnmarked` plus the package-info fallback, reading from both `RuntimeVisibleAnnotations` and `RuntimeInvisibleAnnotations` (JSpecify scope annotations are `@Retention(RUNTIME)`).
- **Nullness resolution.** New `GetMethodReturnNullness`/`GetParameterNullness`/`GetFieldNullness` helpers combine declaration-level `@NonNull`, top-level `TYPE_USE` `@Nullable`/`@NonNull` annotations, and the scope default. Reference-typed slots in a null-marked scope default to `not-null="true"`; explicit `@Nullable` opts back out.

## Trade-offs and deliberately deferred

To keep the change small enough to land, the following are out of scope and tracked as known limitations (one test, `PackageMarked_NullUnmarkedMethod_RevertsToUnknown`, pins down the current behavior so it's easy to find when extending):

- Method-level `@NullUnmarked` is not honored (only class and package scope).
- Module-level `@NullMarked` is not honored.
- Inner-class scope inheritance from the enclosing class is not implemented.
- Type-path-aware nullness (e.g. `Map<@nullable K, V>` for inner generic type arguments) is intentionally only respected at the top of the type. The parser reads `type_path` correctly so `@Nullable String[]` vs `String @nullable[]` are distinguished by `AppliesToTopLevelType` (`type_path_length == 0`), which is the case that matters for surface API nullness.

## Tests

10 new tests in `JSpecifyAnnotationTests.cs` covering package-level `@NullMarked`, class-level `@NullMarked`, primitive exclusion, `@Nullable` opt-out at each slot kind, control unmarked classes, and direct verification that the new type-annotation attribute parses. All 92 `Xamarin.Android.Tools.Bytecode-Tests` pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…1447)

Related to #1448

## Summary
- Remove the standalone `src/Java.Runtime.Environment` project and the desktop/JRE runtime path it provided.
- Remove the samples, performance tests, dynamic tests, and Java.Base tests that depended on the old standalone runtime surface.
- Keep `Java.Interop-Tests` in the solution and CI, and restore them to a passing state without bringing back the `Java.Runtime.Environment` assembly.
- Remove the `Java.Runtime.Environment` friend-assembly dependency from `Java.Interop`; keep only the `Java.Interop-Tests` friend access needed by the restored tests.
- Trim `src/java-interop` down to the native source subset still consumed by dotnet/android, while keeping a minimal validation project so those sources continue to compile in this repo.
- Keep a minimal `Hello-NativeAOTFromJNI` smoke sample that proves Java can load a NativeAOT library, initialize `JniRuntime` from an existing `JNIEnv*`, and call into a native C# method.

## Note on `tests/TestJVM/TestJVM.cs`
This file intentionally grew because it now owns the small amount of JVM-hosting/runtime plumbing that `Java.Interop-Tests` still need after deleting `Java.Runtime.Environment`.

The new code is test-only and replaces the old `TestJVM : JreRuntime` dependency with a direct `TestJVM : JniRuntime` host. It keeps only the pieces needed by the core tests:

- `JNI_CreateJavaVM` loading through `NativeLibrary`
- classpath setup for `java-interop.jar` and `interop-test.jar`
- JDK/JVM path discovery from `JdkInfo.props` or installed JDKs
- a minimal object reference manager for global/weak-global reference counts
- a minimal managed value manager for peer registration/lookup/finalization
- a test type manager for the explicit Java type mappings used by the suite

It deliberately does **not** reintroduce the old product/runtime API surface such as `JreRuntime`, `JreRuntimeOptions`, Mono runtime managers, standalone runtime packaging, reference-log plumbing, or the native `java-interop` JVM loader path. The restored tests/test host have no remaining `Java.Runtime.Environment` references.

## Note on `samples/Hello-NativeAOTFromJNI`
The old sample depended on `Java.Runtime.Environment`, so this PR restores it as a smaller smoke test instead of bringing back the old sample wholesale.

The new sample:

- publishes a NativeAOT shared library,
- lets Java load it with `System.loadLibrary("Hello-NativeAOTFromJNI")`,
- initializes a sample-local `JniRuntime` from the existing `JNIEnv*`, and
- calls a native C# `sayHello()` method that returns a Java string.

It does not exercise managed peer construction or generated JCWs; those can be added later with explicit support if we want a broader NativeAOT/JNI sample.

## Validation
- `dotnet build -t:Prepare`
- `dotnet build src/java-interop/java-interop.csproj`
- `dotnet build src/Java.Interop/Java.Interop.csproj --no-restore`
- `dotnet build tests/Java.Interop-Tests/Java.Interop-Tests.csproj --no-restore`
- `dotnet test tests/Java.Interop-Tests/Java.Interop-Tests.csproj --no-build` — 669 passed, 0 failed, 4 skipped
- `dotnet publish -c Release -r osx-arm64 samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj`
- `dotnet build -c Release -r osx-arm64 -t:RunJavaSample samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj --no-restore`

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers-java-interop-migration-plan branch 3 times, most recently from 5bf0a0e to 220f39b Compare June 26, 2026 18:11
Bumps [external/xamarin-android-tools](https://github.com/xamarin/xamarin-android-tools) from `5165523` to `bcce35f`.
- [Commits](dotnet/android-tools@5165523...bcce35f)

---
updated-dependencies:
- dependency-name: external/xamarin-android-tools
  dependency-version: bcce35f51c67671ca4846fc8ea0dbe140ea9542a
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers-java-interop-migration-plan branch from 220f39b to 71db2de Compare June 27, 2026 01:55
@jonathanpeppers

Copy link
Copy Markdown
Member Author

Output Binary Comparison

Compared the artifacts from this PR's dotnet-android build (#1483569, ji-migration on top of merge-base c2f553d7) against a sibling PR's build (#1483574, PR #11761, branched from current main with no ji changes). Both built within minutes of each other on the same agents.

dotnet/android binaries — effectively identical

Artifact This PR (1483569) Baseline PR (1483574) Δ
nuget-unsigned 340,284,168 340,283,987 +181 B
nuget-unsigned-symbols 364,729,281 364,714,723 +14.5 KB
test-assemblies 576,572,509 576,549,009 +23.5 KB
windows-toolchain-pdb 484,696,718 484,696,718 0 — byte identical
nuget-linux-unsigned 126,878,161 126,878,133 +28 B
nuget-linux-unsigned-symbols 127,860,399 127,853,430 +7 KB
AndroidBuildToolsInventory 149 149 identical

Per-package breakdown of nuget-unsigned (18 packages on each side, all matching by name modulo PR-number tag) — largest delta is 68 bytes on the 145 MB Microsoft.Android.Sdk.Darwin nupkg. All within normal non-deterministic packaging variance (zip timestamps, PDB GUIDs).

Java.Interop lanes vs upstream dotnet.java-interop

Compared this PR's Java.Interop Tests Windows - .NET / Mac - .NET jobs (build 1483569) against upstream dotnet.java-interop build 1477466 at the exact same SHA (70493645c7d95648010a4cef948234a28744c03f — the SHA the submodule was pinned to).

  • Steps: Identical step lists in both jobs (Init → Checkout → Use .NET → Prepare Solution → Build Solution → ~22 Tests/Publish pairs → java-source-utils → NativeAOT publish/run → JUnit → fail-on-dirty-tree → fail-on-issue). Only differences are auto-injected pipeline policy tasks (CodeQL upstream, Component Detection here) — not template-driven.
  • TestRelease binaries (test outputs): same file counts (1271 / 1271), 3 MB size delta from non-deterministic packaging.
  • BuildRelease and BuildRelease-net10.0: byte-identical sizes (22 files / 0.4 MB and 5 files / 0.1 MB).
  • Release-net10.0 (utility tool binaries): missing 96 files (14.6 MB) here vs upstream — generator.dll, class-parse.dll, jcw-gen.dll, logcat-parse.dll, Java.Interop.Tools.Generator.dll, etc. They're still being built (verified in Build Solution log) but are redirected to bin/Release/lib/packs/Microsoft.Android.Sdk.Windows/37.0.0/tools/ because external/Java.Interop/Directory.Build.props imports external/Java.Interop.override.props, which sets UtilityOutputFullPathCoreApps to the dotnet/android SDK pack tree. This is intentional dotnet/android behavior (so ji tools land in the SDK pack); the ji standalone CI lane just inherits it. Tests still pass.

The Windows ji job's PublishPipelineArtifact@1 step was dropped — only logs/tests needed.

@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers-java-interop-migration-plan branch 5 times, most recently from 2f63128 to 7fc6649 Compare July 1, 2026 13:07
Adds a shared 'Java.Interop Tests' stage template at
build-tools/automation/yaml-templates/stage-java-interop-tests.yaml
that runs the two jobs from the upstream java-interop pipeline
(Windows - .NET, Mac - .NET) using the in-tree
external/Java.Interop/build-tools/automation/templates/ files.

The stage is wired into both the official pipeline (azure-pipelines.yaml)
and the public PR validation pipeline (azure-pipelines-public.yaml) so
they stay in lockstep with zero duplication.

A follow-up PR will reorganize external/Java.Interop and dedupe its
nested external/* submodules against dotnet/android's own external/.
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers-java-interop-migration-plan branch from 7fc6649 to 41c5962 Compare July 1, 2026 14:00
@jonathanpeppers jonathanpeppers marked this pull request as ready for review July 1, 2026 14:00
Copilot AI review requested due to automatic review settings July 1, 2026 14:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@jonathanpeppers jonathanpeppers merged commit 1f661d7 into main Jul 1, 2026
43 checks passed
@jonathanpeppers jonathanpeppers deleted the jonathanpeppers-java-interop-migration-plan branch July 1, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

use-merge-commit Normally we squash-and-merge PRs. Use this label so we instead use a merge commit.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants